/**
 *    Copyright 2009-2015 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.builder.xml;

import java.io.InputStream;
import java.io.Reader;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.ibatis.builder.BaseBuilder;
import org.apache.ibatis.builder.BuilderException;
import org.apache.ibatis.datasource.DataSourceFactory;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.loader.ProxyFactory;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaClass;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.session.AutoMappingBehavior;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.LocalCacheScope;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.type.JdbcType;

/**
 * @author Clinton Begin
 */
public class XMLConfigBuilder extends BaseBuilder {//mybatis-config.xml文件的解析
// 构造函数传递的参数可以是类的所有成员变量，一般把仅需要设置一次的成员变量作为构造函数的参数，而可以设置多次的成员变量用set方法设置，也可以把仅需要设置一次但不需要传递参数的成员变量在普通代码块中设置默认值
// 构造函数中可以执行其他类的构造函数，也可把多个参数作为内部构造函数的参数
// 执行顺序:类中静态代码块、普通代码块、实例代码块
//  委托模式:委托者持有被委托者的引用，委托模式注重过程

// 实例方法的参数可以是do what中的what

  private boolean parsed;
  private XPathParser parser;
  private String environment;
  private ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();

  public XMLConfigBuilder(Reader reader) {
    this(reader, null, null);
  }

  public XMLConfigBuilder(Reader reader, String environment) {
    this(reader, environment, null);
  }

  public XMLConfigBuilder(Reader reader, String environment, Properties props) {//SqlSessionFactoryBuilder中调用
    this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
  }

  public XMLConfigBuilder(InputStream inputStream) {
    this(inputStream, null, null);
  }

  public XMLConfigBuilder(InputStream inputStream, String environment) {
    this(inputStream, environment, null);
  }

  public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {//SqlSessionFactoryBuilder中调用
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }

  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {//解析时会初始化用new Configuration初始化configuration
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");//ErrorContext
    this.configuration.setVariables(props);//将外部环境的变量设置给configuration
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
//      configuration.setVariables:properties标签，子标签property、属性resource和url，两属性如果同时不存在会报异常,如果两者都存在则只会加载resource的properties；
//      子元素property、属性resource或属性url、configuration的variables属性会依次加载，后者会把前者相同的属性覆盖
      propertiesElement(root.evalNode("properties"));//  根据resource或者url属性加载并解析外部prop文件
//      typeAliasRegistry.registerAlias:将参数处理和结果映射的类取别名后注册到typeAliasRegistry中，如果有两个key相同，类名不同的value将会报异常，这样避免加载出错的问题；类加载可以包扫描或者指定类全限定名加载，二者只能选择其中一个:包扫描会扫描该包及子包下的类，全限定名只会加载该类文件
      typeAliasesElement(root.evalNode("typeAliases"));//  扫描注册package下面的class，或者根据alias和type属性加载注册相应的class
//      interceptorChain.addInterceptor:将所有的interceptor加载类并实例化然后加入interceptorChain中，解析时会根据全限定名去typeAliasRegistry中寻找类文件，如果没找到将加载该类文件，类文件如果没找到将报错(有properties)
      pluginElement(root.evalNode("plugins"));// 根据interceptor子标签创建Interceptor实例
//      configuration.setObjectFactory：加载对象工厂类(实例化查询结果对应的目标类)并实例化，设置对象工厂(有properties),默认为DefaultObjectFactory
      objectFactoryElement(root.evalNode("objectFactory"));// 根据type属性创建ObjectFactory实例
//      加载对象加工工厂类(对于指定的对象进行特殊的加工)并实例化，设置对象加工工厂(无properties),默认为DefaultObjectWrapperFactory
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));// 根据type属性创建ObjectWrapperFactory实例
//      加载反射工厂类(简化反射操作的相关代码)并实例化，设置反射工厂(无properties),默认为DefaultReflectorFactory
      reflectionFactoryElement(root.evalNode("reflectionFactory"));// 根据type属性创建ReflectorFactory实例
//      全局设置初始化并加载,加载configuration类文件,并看settings中是否有不在configuration中的属性(set),如果没有就抛出异常
//      configuration.class共有28个set属性,settings中可以设置以下22个:
//      autoMappingBehavior  cacheEnabled  proxyFactory  lazyLoadingEnabled  aggressiveLazyLoading
//      multipleResultSetsEnabled  useColumnLabel  useGeneratedKeys  defaultExecutorType  defaultStatementTimeout
//      defaultFetchSize  mapUnderscoreToCamelCase  safeRowBoundsEnabled  localCacheScope  jdbcTypeForNull
//      lazyLoadTriggerMethods  safeResultHandlerEnabled  defaultScriptingLanguage  callSettersOnNulls  logPrefix
//      logImpl  configurationFactory
      settingsElement(root.evalNode("settings"));
//       read it after objectFactory and objectWrapperFactory issue #631
//      环境设置：如开发、本地、测试、default，包括id、事务、数据源的设置
      environmentsElement(root.evalNode("environments"));//Environment.Builder():建造者模式
//      databaseIdProvider设置,数据库标识设置,提供多数据源支持。DatabaseIdProvider:mybatis的实现类为VendorDatabaseIdProvider,可以配置type属性为VENDOR,再进行配置
//      当databaseIdProvider节点配置了DatabaseIdProvider的实现类以及environment中配置了dataSource时，就会用environment的dataSource.getConnection().getMetaData().getDatabaseProductName()作为key到databaseIdProvider的properties中获取到的value，设置configuration的databaseId
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//      读取javaType、jdbcType、handler并转换，加载javaType和handle对应的类，然后注册到typeHandlerRegistry中
//      typeHandlerRegistry有三个map作为属性:JDBC_TYPE_HANDLER_MAP(key为JdbcType对象,value为ypeHandle对象)、TYPE_HANDLER_MAP(key为JavaType对象,value为一个map,其中key为JdbcType对象,value为ypeHandle对象)、ALL_TYPE_HANDLERS_MAP(key为typeHandle.class,value为typeHandle对象)
      typeHandlerElement(root.evalNode("typeHandlers"));
//      加载mapper.xml映射文件并解析或将mapper.class文件加载并注册到MapperRegistry,将xml文件缓存到loadedResources中,加载mapper相关组件
//      解析及组件类加载的详细过程:
//      如果是package或class见MapperAnnotationBuilder类的parse()方法    mapperRegistry
//      如果是resource或url见XmlMapperBuild类的parse()方法,
      mapperElement(root.evalNode("mappers"));//XMLMapperBuilder.parser  configuration.addMapper
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

  private void typeAliasesElement(XNode parent) {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String typeAliasPackage = child.getStringAttribute("name");
          configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
        } else {
          String alias = child.getStringAttribute("alias");
          String type = child.getStringAttribute("type");
          try {
            Class<?> clazz = Resources.classForName(type);
            if (alias == null) {
              typeAliasRegistry.registerAlias(clazz);
            } else {
              typeAliasRegistry.registerAlias(alias, clazz);
            }
          } catch (ClassNotFoundException e) {
            throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
          }
        }
      }
    }
  }

  private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        String interceptor = child.getStringAttribute("interceptor");
        Properties properties = child.getChildrenAsProperties();//获取properties
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
        interceptorInstance.setProperties(properties);//设置properties
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }

  private void objectFactoryElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type");
      Properties properties = context.getChildrenAsProperties();//获取properties
      ObjectFactory factory = (ObjectFactory) resolveClass(type).newInstance();
      factory.setProperties(properties);//设置properties
      configuration.setObjectFactory(factory);
    }
  }

  private void objectWrapperFactoryElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type");
      ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).newInstance();
      configuration.setObjectWrapperFactory(factory);
    }
  }

  private void reflectionFactoryElement(XNode context) throws Exception {
    if (context != null) {
       String type = context.getStringAttribute("type");
       ReflectorFactory factory = (ReflectorFactory) resolveClass(type).newInstance();
       configuration.setReflectorFactory(factory);
    }
  }

  private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
      Properties defaults = context.getChildrenAsProperties();
      String resource = context.getStringAttribute("resource");
      String url = context.getStringAttribute("url");
      if (resource != null && url != null) {
        throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
      }
      if (resource != null) {
        defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if (url != null) {
        defaults.putAll(Resources.getUrlAsProperties(url));
      }
      Properties vars = configuration.getVariables();
      if (vars != null) {
        defaults.putAll(vars);
      }
      parser.setVariables(defaults);// 设置Xpath的properties
      configuration.setVariables(defaults);
    }
  }

  private void settingsElement(XNode context) throws Exception {
    if (context != null) {
      Properties props = context.getChildrenAsProperties();
      // Check that all settings are known to the configuration class
      MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
      for (Object key : props.keySet()) {
        if (!metaConfig.hasSetter(String.valueOf(key))) {
          throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
        }
      }
      configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
      configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
//      可以用于自定义处理懒加载问题的ProxyFactory的实现类,mybatis有两种实现JavassistProxyFactory和CglibProxyFactory,默认为JavassistProxyFactory
      configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
      configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
      configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), true));
      configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
      configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
      configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
      configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
      configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
      configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
      configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
      configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
      configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));//默认为Session
      configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
      configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
      configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
      configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));// 默认为XMLLanguageDriver
      configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
      configuration.setLogPrefix(props.getProperty("logPrefix"));
      configuration.setLogImpl(resolveClass(props.getProperty("logImpl")));
      configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
    }
  }

  private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
      if (environment == null) {
        environment = context.getStringAttribute("default");
      }
      for (XNode child : context.getChildren()) {
        String id = child.getStringAttribute("id");
        if (isSpecifiedEnvironment(id)) {
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          DataSource dataSource = dsFactory.getDataSource();
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          configuration.setEnvironment(environmentBuilder.build());
        }
      }
    }
  }

  private void databaseIdProviderElement(XNode context) throws Exception {
    DatabaseIdProvider databaseIdProvider = null;
    if (context != null) {
      String type = context.getStringAttribute("type");
      // awful patch to keep backward compatibility
      if ("VENDOR".equals(type)) {
          type = "DB_VENDOR";
      }
      Properties properties = context.getChildrenAsProperties();//获取properties
      databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
      databaseIdProvider.setProperties(properties);//设置properties
    }
    Environment environment = configuration.getEnvironment();
    if (environment != null && databaseIdProvider != null) {
      String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
      configuration.setDatabaseId(databaseId);
    }
  }

  private TransactionFactory transactionManagerElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type");
      Properties props = context.getChildrenAsProperties();
      TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
      factory.setProperties(props);
      return factory;
    }
    throw new BuilderException("Environment declaration requires a TransactionFactory.");
  }

  private DataSourceFactory dataSourceElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type");
      Properties props = context.getChildrenAsProperties();
      DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
      factory.setProperties(props);
      return factory;
    }
    throw new BuilderException("Environment declaration requires a DataSourceFactory.");
  }

  private void typeHandlerElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String typeHandlerPackage = child.getStringAttribute("name");
          typeHandlerRegistry.register(typeHandlerPackage);
        } else {
          String javaTypeName = child.getStringAttribute("javaType");
          String jdbcTypeName = child.getStringAttribute("jdbcType");
          String handlerTypeName = child.getStringAttribute("handler");
          Class<?> javaTypeClass = resolveClass(javaTypeName);
          JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
          Class<?> typeHandlerClass = resolveClass(handlerTypeName);
          if (javaTypeClass != null) {
            if (jdbcType == null) {
              typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);// @MappedJdbcTypes注解
            } else {
              typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
            }
          } else {
            typeHandlerRegistry.register(typeHandlerClass);// @MappedTypes注解
          }
        }
      }
    }
  }

  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {//MapperAnnotationBuilder
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);//ErrorContext
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());//mappper.xml文件流，configuration，resource路径，sqlFragments结果集(所有mapper.xml文件中的sql标签节点)
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);//ErrorContext
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());//mappper.xml文件流，configuration，url路径，sqlFragments结果集(所有mapper.xml文件中的sql标签节点)
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

  private boolean isSpecifiedEnvironment(String id) {
    if (environment == null) {
      throw new BuilderException("No environment specified.");
    } else if (id == null) {
      throw new BuilderException("Environment requires an id attribute.");
    } else if (environment.equals(id)) {
      return true;
    }
    return false;
  }

}
